/*	$Id: pcap.c 6 2008-07-29 02:30:10Z phrakt $	*/
/*
 * Copyright (c) 2004 Jean-Francois Brousseau <jfb@openbsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * PCAP file support
 *
 * The functions provided here can be used to read from and write to
 * tcpdump(1)-format packet dump files.
 *
 * The header portion of pcap files is written in host byte-order, but
 * packets are dumped in network byte-order.
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>

#include <net/bpf.h>	/* for DLT_* types */

#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

#include "pkt.h"
#include "dlink.h"
#include "pforge.h"
#include "private.h"

#define PCAP_MAGIC     0xa1b2c3d4

#define PCAP_BUFSIZE   8192

/* supported version */
#define PCAP_VERSION_MAJOR 2
#define PCAP_VERSION_MINOR 4

#define PCAP_SWAPHDR(h)					\
	do {						\
		(h)->pc_magic = swap16((h)->pc_magic);	\
		(h)->pc_major = swap16((h)->pc_major);	\
		(h)->pc_minor = swap16((h)->pc_minor);	\
		(h)->pc_zone = swap32((h)->pc_zone);	\
		(h)->pc_tsacc = swap32((h)->pc_tsacc);	\
		(h)->pc_snlen = swap32((h)->pc_snlen);	\
		(h)->pc_dlt = swap32((h)->pc_dlt);	\
	} while (0)

/* pcap file header */
struct pf_pcap_fhdr {
	uint32_t  pc_magic;
	uint16_t  pc_major;
	uint16_t  pc_minor;
	int32_t   pc_zone;	/* timezone */
	uint32_t  pc_tsacc;	/* timestamp accuracy */
	uint32_t  pc_snlen;	/* snapshot length */
	uint32_t  pc_dlt;	/* datalink type */
};

/* pcap packet header */
struct pf_pcap_phdr {
	struct timeval pc_ts;		/* timestamp */
	uint32_t       pc_caplen;	/* capture length */
	uint32_t       pc_rlen;		/* original packet length */
};

#define PFPCAP_FL_BOSWAP  0x0001	/* swap byte order of header */
#define PFPCAP_FL_EOS     0x0002

struct pf_pcap_file {
	char  *pf_path;
	int    pf_flags;
	int    pf_fd;

	/* read buffer, equal to snapshot length */
	void  *pf_rbuf;
	size_t pf_rblen;
	size_t pf_rbused;

	struct pf_pcap_fhdr pf_hdr;

	/* buffered packet chains */
	PF_PKTQ             pf_inq;
	PF_PKTQ             pf_outq;

#ifdef PF_THREAD_SAFE
	pthread_mutex_t     pf_mtx;
#endif
};

struct pf_pcap_dltmap {
	int  dm_dlt;
	int  dm_pfv;
} pcap_dltmap[] = {
	{ DLT_NULL,             0 },
	{ DLT_EN10MB,           0 },
	{ DLT_EN3MB,            0 },
	{ DLT_AX25,             0 },
	{ DLT_PRONET,           0 },
	{ DLT_CHAOS,            0 },
	{ DLT_IEEE802,          0 },
	{ DLT_ARCNET,           0 },
	{ DLT_SLIP,             0 },
	{ DLT_PPP,              0 },
	{ DLT_FDDI,             0 },
	{ DLT_ATM_RFC1483,      0 },
	{ DLT_LOOP,             0 },
	{ DLT_ENC,              0 },
	{ DLT_RAW,              0 },
	{ DLT_SLIP_BSDOS,       0 },
	{ DLT_PPP_BSDOS,        0 },
	{ DLT_OLD_PFLOG,        0 },
	{ DLT_PFSYNC,           0 },
	{ DLT_PPP_ETHER,        0 },
	{ DLT_IEEE802_11,       0 },
	{ DLT_PFLOG,            0 },
	{ DLT_IEEE802_11_RADIO, 0 },
};

static int pf_pcap_load  (PF_PCAP *);


/*
 * pf_pcap_open()
 *
 * Open the packet capture file <file> with optional flags set in <flags>,
 * which are the same as for the open(2) system call, with the exception that
 * a file may only be opened either for reading or writing but not both.
 * Returns a pointer to the pcap file handle on success, or NULL on failure.
 */
PF_PCAP*
pf_pcap_open(const char *file, int flags)
{
	int ret;
	PF_PCAP *pcap;

	/* we don't support both read and write simultaneously yet */
	if ((flags & O_ACCMODE) == O_RDWR) {
		pf_seterr(PF_ERR_INVARG);
		return (NULL);
	}

	if ((pcap = (PF_PCAP *)pf_malloc(sizeof(*pcap))) == NULL) {
		return (NULL);
	}
	memset(pcap, 0, sizeof(*pcap));

	TAILQ_INIT(&(pcap->pf_inq));
	TAILQ_INIT(&(pcap->pf_outq));

#ifdef PF_THREAD_SAFE
	if ((ret = pthread_mutex_init(&(pcap->pf_mtx), NULL)) != 0) {
		pf_free(pcap);
		return (NULL);
	}
#endif

	if ((pcap->pf_path = strdup(file)) == NULL) {
		(void)pf_pcap_close(pcap);
		pf_seterr(PF_ERR_NOMEM);
		return (NULL);
	}

	pcap->pf_flags = flags;

	if (((flags & O_ACCMODE) == O_RDONLY) && (pf_pcap_load(pcap) == -1)) {
		(void)pf_pcap_close(pcap);
		return (NULL);
	}

	return (pcap);
}

/*
 * pf_pcap_close()
 *
 * Close a handle to a pcap-format file previously opened by pf_pcap_open().
 * No further operations may be made on <pcap> after this call returns.
 * Returns 0 on success, or -1 on failure.
 */
int
pf_pcap_close(PF_PCAP *pcap)
{
	if (pcap == NULL) {
		pf_seterr(PF_ERR_INVARG);
		return (-1);
	}

#ifdef PF_THREAD_SAFE
	(void)pthread_mutex_destroy(&(pcap->pf_mtx));
#endif
	if (pcap->pf_fd >= 0)
		(void)close(pcap->pf_fd);
	if (pcap->pf_path != NULL)
		pf_free(pcap->pf_path);
	pf_free(pcap);

	return (0);
}

/*
 * pf_pcap_getsnaplen()
 * pf_pcap_setsnaplen()
 *
 * Set or retrieve the snapshot length associated with the pcap file <pcap>.
 */
int
pf_pcap_getsnaplen(PF_PCAP *pcap)
{
	return (pcap->pf_hdr.pc_snlen);
}

int
pf_pcap_setsnaplen(PF_PCAP *pcap, int len)
{
	pcap->pf_hdr.pc_snlen = len;
	return (0);
}

/*
 * pf_pcap_getdlt()
 * pf_pcap_setdlt()
 *
 * Set or retrieve the datalink type associated with the pcap file <pcap>.
 */
int
pf_pcap_getdlt(PF_PCAP *pcap)
{
	return (pcap->pf_hdr.pc_dlt);
}

int
pf_pcap_setdlt(PF_PCAP *pcap, int dlt)
{
	pcap->pf_hdr.pc_dlt = dlt;
	return (0);
}

/*
 * pf_pcap_dlt2pf()
 *
 * Resolve the pcap DLT_* type to the equivalent in the Packet Forge library.
 */
int
pf_pcap_dlt2pf(int dlt)
{
	return (0);
}

/*
 * pf_pcap_readpkt()
 *
 * Read the next packet available from the pcap dump file handle <pcap>.
 * To accelerate things a bit, the function actually reads the file by
 * larger chunks and buffers any following packets for subsequent reads.
 * Returns a pointer to the next packet on success, NULL on failure, or
 * PFPCAP_EOS (End Of Stream) if no more packets are available.
 */
PF_PKT*
pf_pcap_readpkt(PF_PCAP *pcap)
{
	ssize_t ret;
	size_t left;
	uint8_t rbuf[PCAP_BUFSIZE], *rbp, *rendp;
	struct pf_pcap_phdr *php;
	PF_PKT *pkt;

#ifdef PF_THREAD_SAFE
	if ((ret = pthread_mutex_lock(&(pcap->pf_mtx))) != 0) {
		return (NULL);
	}
#endif

	/* clear the error code */
	pf_seterr(PF_ERR_NOERR);

	/* grab a buffered packet right away */
	if (!TAILQ_EMPTY(&(pcap->pf_inq)))
		goto readpkt_done;

	rbp = rbuf;
	rendp = rbuf + sizeof(rbuf);

	/* if we have buffered data, store it first */
	if (pcap->pf_rbused > 0) {
		memcpy(rbp, pcap->pf_rbuf, pcap->pf_rbused);
		rbp += pcap->pf_rbused;
		pcap->pf_rbused = 0;
	}

	if ((ret = read(pcap->pf_fd, rbp, rendp - rbp)) == -1) {
		pf_seterr(PF_ERR_INERR);
		goto readpkt_done;
	} else if (ret == 0) {
		pcap->pf_flags |= PFPCAP_FL_EOS;
		goto readpkt_done;
	}

	/*
	 * Extract the information for each packet found in the read buffer
	 * and queue the generated packets on the input queue.
	 */
	rendp = rbp + (size_t)ret;
	rbp = rbuf;
	do {
		php = (struct pf_pcap_phdr *)rbp;
		left = (size_t)(rendp - rbp);

		/* do we actually have a full header + packet */
		if ((left < sizeof(*php)) || (left < php->pc_caplen)) {
			if ((left >= sizeof(*php)) &&
			    (php->pc_caplen > pcap->pf_hdr.pc_snlen)) {
				pf_seterr(PF_ERR_INERR);
				goto readpkt_done;
			}
			/* XXX this will squash previously buffered data
			 * but at this point, it means we haven't read a
			 * full packet in the last 2 read() calls, so
			 * something funky is going on...this will still
			 * need to be fixed
			 */
			memcpy(pcap->pf_rbuf, rbp, left);
			pcap->pf_rbused = left;
			break;
		}

		rbp += sizeof(*php);

		if ((pkt = pf_pkt_alloc()) == NULL)
			goto readpkt_done;

		pkt->pkt_len = php->pc_caplen;
		pkt->pkt_ts.tv_sec = php->pc_ts.tv_sec;
		pkt->pkt_ts.tv_nsec = php->pc_ts.tv_usec * 1000;
		if (pf_pkt_set(pkt, rbp, php->pc_caplen) < 0)
			goto readpkt_done;

		TAILQ_INSERT_TAIL(&(pcap->pf_inq), pkt, pkt_link);

		rbp += php->pc_caplen;

	} while (rbp < rendp);

readpkt_done:
	if (pf_geterr() == PF_ERR_NOERR) {
		pkt = TAILQ_FIRST(&(pcap->pf_inq));
		if (pkt != NULL)
			TAILQ_REMOVE(&(pcap->pf_inq), pkt, pkt_link);
		else {
			if (pcap->pf_rbused > 0) {
				pcap->pf_rbused = 0;
				pf_seterr(PF_ERR_INERR);
				pkt = NULL;
			} else
				pkt = PFPCAP_EOS;
		}
	}

#ifdef PF_THREAD_SAFE
	(void)pthread_mutex_unlock(&(pcap->pf_mtx));
#endif

	return (pkt);

}

/*
 * pf_pcap_writepkt()
 *
 * Write the packet <pkt> to the pcap file handle <pcap>.
 * Returns 0 on success, or -1 on failure.
 */
int
pf_pcap_writepkt(PF_PCAP *pcap, PF_PKT *pkt)
{
	pf_seterr(PF_ERR_NOTSUP);
	return (-1);
}

/*
 * pf_pcap_load()
 *
 * Perform loading of a pcap dump file from which packets will be read by
 * reading the header and initializing the appropriate fields.
 */
static int
pf_pcap_load(PF_PCAP *pcap)
{
	ssize_t ret;

	if ((pcap->pf_fd = open(pcap->pf_path, pcap->pf_flags, 0640)) == -1) {
		if (errno == ENOENT)
			pf_seterr(PF_ERR_NOFILE);
		else if (errno == EACCES)
			pf_seterr(PF_ERR_NOPERM);
		return (-1);
	}

	if ((ret = read(pcap->pf_fd, &(pcap->pf_hdr),
	    sizeof(pcap->pf_hdr))) == -1) {
		return (-1);
	} else if (ret < (ssize_t)sizeof(pcap->pf_hdr)) {
		pf_seterr(PF_ERR_DATAERR);
		return (-1);
	}

	if (pcap->pf_hdr.pc_magic != PCAP_MAGIC) {
		/* check if the file was written in another endianness */
		if (pcap->pf_hdr.pc_magic == swap32(PCAP_MAGIC)) {
			pcap->pf_flags |= PFPCAP_FL_BOSWAP;
			PCAP_SWAPHDR(&(pcap->pf_hdr));
		} else {
			pf_seterr(PF_ERR_DATAERR);
			return (-1);
		}
	}

	if ((pcap->pf_hdr.pc_major != PCAP_VERSION_MAJOR) ||
	    (pcap->pf_hdr.pc_minor != PCAP_VERSION_MINOR)) {
		pf_seterr(PF_ERR_VERSION);
		return (-1);
	}

	/* allocate a read buffer as large as the datalink snapshot length */
	if ((pcap->pf_rbuf = pf_malloc(pcap->pf_hdr.pc_snlen)) == NULL) {
		return (NULL);
	}
	pcap->pf_rblen = pcap->pf_hdr.pc_snlen;

	return (0);
}
